package per.sunmes.lesrb.manager;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;

import per.sunmes.lesrb.apis.CallBackObj;
import per.sunmes.lesrb.util.GM;

/** SoundManager */
public class SM {
	private static final String TAG = "SoundManager";
	private static SM _instance;

	/** When sound play called ,this will be set. */
	public static long lastSoundId;

	private static SM I() {
		if (_instance == null) {
			_instance = new SM();
		}
		return _instance;
	}

	public static void dispose() {
		if (_instance != null) {
			_instance._dispose();
			_instance = null;
		}
	}

	public static boolean isMusicOn() {
		return I().isMusicOn;
	}

	public static boolean isSoundOn() {
		return I().isSoundOn;
	}

	public static void setMusicOn(boolean isMusicOn) {
		I()._setMusicOn(isMusicOn);
	}

	public static void setSoundOn(boolean isSoundOn) {
		I()._setSoundOn(isSoundOn);
	}

	public static void stopMusic() {
		I()._stopMusic();
	}

	public static void addMusicStateChangeCall(CallBackObj<Boolean> call) {
		if (!I().musicStateChangeCalls.contains(call, true)) {
			I().musicStateChangeCalls.add(call);
		}
	}

	public static void removeMusicStateChangeCall(CallBackObj<Boolean> call) {
		I().musicStateChangeCalls.removeValue(call, true);
	}

	public static Array<CallBackObj<Boolean>> getMusicStateChangeCalls() {
		return I().musicStateChangeCalls;
	}

	public static void addSoundStateChangeCall(CallBackObj<Boolean> call) {
		if (!I().soundStateChangeCalls.contains(call, true)) {
			I().soundStateChangeCalls.add(call);
		}
	}

	public static void removeSoundStateChangeCall(CallBackObj<Boolean> call) {
		I().soundStateChangeCalls.removeValue(call, true);
	}

	public static Array<CallBackObj<Boolean>> getSoundStateChangeCalls() {
		return I().soundStateChangeCalls;
	}

	public static Music playMusic(String path) {
		return I()._playMusic(path);
	}

	public static Music playMusicOnce(String path) {
		Music music = I()._playMusic(path);
		if (music != null) {
			music.setLooping(false);
		}
		return music;
	}

	public static long cacheAndPlay(Sound sound) {
		return cacheAndPlay(sound, false, 1);
	}

	public static long cacheAndPlay(Sound sound, boolean isLooping) {
		return cacheAndPlay(sound, isLooping, 1);
	}

	public static long cacheAndPlay(Sound sound, boolean isLooping, float volume) {
		if (isSoundOn()) {
			long id = -1;
			if (isLooping) {
				id = sound.loop(getSoundVolume() * volume);
			} else {
				id = sound.play(getSoundVolume() * volume);
			}
			addSoundPlayedCacheId(sound, id);
			return id;
		} else {
			return -1;
		}
	}

	public static void disposeOwnSounds() {
		I()._disposeOwnSounds();
	}

	/**
	 * Will load and cache in ownSoundPool.
	 * 
	 * @param name
	 * @return
	 */
	public static Sound playOwnSound(String name) {
		return I()._playOwnSound(name);
	}

	/**
	 * Will load and cache in ownSoundPool.
	 * 
	 * @param name
	 * @param isLooping
	 * @return
	 */
	public static Sound playOwnSound(String name, boolean isLooping) {
		return I()._playOwnSound(name, isLooping);
	}

	/**
	 * Will load sound to RM.getAssert().
	 * 
	 * @param name
	 * @return
	 */
	public static Sound playAssetSound(String name) {
		return I()._playAssetSound(name);
	}

	public static Sound getLoadAssetSound(String name) {
		return I()._getLoadAssetSound(name);
	}

	public static Sound getLoadOwnSound(String name) {
		return I()._getLoadOwnSound(name);
	}

	/**
	 * Will load sound to RM.getAssert().
	 * 
	 * @param name
	 * @param isLooping
	 * @return
	 */
	public static Sound playAssetSound(String name, boolean isLooping) {
		return I()._playAssetSound(name, isLooping);
	}

	/**
	 * Use for getOwnSound(XX).play(...).
	 * 
	 * @param name
	 * @return
	 */
	public static Sound getOwnSound(String name) {
		return I()._getLoadOwnSound(name);
	}

	public static Map<String, Sound> getOwnSoundPool() {
		return I().ownSoundPool;
	}

	public static String getCurrentMusicPathName() {
		return I().musicName;
	}

	public static Music getCurrentMusic() {
		return I().music;
	}

	public static float getMusicVolume() {
		return I().musicVolume;
	}

	public static float getSoundVolume() {
		return I().soundVolume;
	}

	public static void setMusicVolume(float volume) {
		I()._setMusicVolume(volume);
	}

	public static void setSoundVolume(float volume) {
		I()._setSoundVolume(volume);
	}

	public static Map<Sound, Array<Long>> getSoundHasPlayedIdCaches() {
		return I().soundHasPlayedIdCaches;
	}

	public static void addSoundPlayedCacheId(Sound sound, long id) {
		I()._addSoundPlayedIdCache(sound, id);
	}

	public static void stopAllSoundPlayedInCache() {
		I()._stopAllCachedSounds();
	}

	public static void pauseAllSoundPlayedInCache() {
		I()._pauseAllCachedSounds();
	}

	public static void resumeAllSoundPlayedInCache() {
		I()._resumeAllCachedSounds();
	}

	public static void pauseMusic() {
		I()._pauseMusic();
	}

	public static void resumeMusic() {
		I()._resumeMusic();
	}

	///////////////////
	boolean isMusicOn = true, isSoundOn = true;
	Array<CallBackObj<Boolean>> musicStateChangeCalls = new Array<CallBackObj<Boolean>>();
	Array<CallBackObj<Boolean>> soundStateChangeCalls = new Array<CallBackObj<Boolean>>();

	Map<Sound, Array<Long>> soundHasPlayedIdCaches = new HashMap<Sound, Array<Long>>();

	String musicName;
	Music music;
	Map<String, Sound> ownSoundPool = new HashMap<String, Sound>();
	float musicVolume = 1;
	float soundVolume = 1;

	private void _setMusicVolume(float volume) {
		if (volume < 0) {
			volume = 0;
		}
		if (volume > 1) {
			volume = 1;
		}
		musicVolume = volume;
		if (music != null) {
			music.setVolume(musicVolume);
		}
		if (volume == 0 && isMusicOn) {
			setMusicOn(false);
		} else if (!isMusicOn) {
			setMusicOn(true);
		}
	}

	private void _setSoundVolume(float volume) {
		if (volume < 0) {
			volume = 0;
		}
		if (volume > 1) {
			volume = 1;
		}
		soundVolume = volume;
		if (volume == 0 && isSoundOn) {
			setSoundOn(false);
		} else if (!isSoundOn) {
			setSoundOn(true);
		}
	}

	private Sound _getLoadAssetSound(String name) {
		Sound sound = null;
		try {
			if (!RM.getAsset().isLoaded(name, Sound.class)) {
				RM.getAsset().load(name, Sound.class);
				RM.getAsset().finishLoadingAsset(name);
			}
			sound = RM.getAsset().get(name, Sound.class);
		} catch (Exception e) {
			e.printStackTrace();
			GM.logger.error(TAG, "getLoadAssetSound> asset load sound named [" + name + "] faild!");
		}
		return sound;
	}

	private Sound _getLoadOwnSound(String name) {
		if (!ownSoundPool.containsKey(name)) {
			FileHandle fh = RM.getFile(name);
			if (!fh.exists()) {
				GM.logger.error(TAG, "getLoadOwnSound> sound named [" + name + "] not found!");
				return null;
			}
			Sound sound = Gdx.audio.newSound(RM.getFile(name));
			if (sound == null) {
				GM.logger.error(TAG, "getLoadOwnSound> sound named [" + name + "] load error!");
				return null;
			}
			ownSoundPool.put(name, sound);
		}
		return ownSoundPool.get(name);
	}

	private void _stopMusic() {
		if (music != null) {
			music.stop();
		}
	}

	private void _pauseMusic() {
		if (music != null) {
			music.pause();
		}
	}

	private void _resumeMusic() {
		if (music != null && isMusicOn) {
			music.play();
		}
	}

	private Sound _playAssetSound(String name) {
		return _playAssetSound(name, false);
	}

	private Sound _playAssetSound(String name, boolean isLooping) {
		Sound sound = _getLoadAssetSound(name);
		if (sound != null && isSoundOn) {
			long id = sound.play(soundVolume);
			sound.setLooping(id, isLooping);
			_addSoundPlayedIdCache(sound, id);
			lastSoundId = id;
		}
		return sound;
	}

	private void _addSoundPlayedIdCache(Sound sound, long id) {
		Array<Long> array = null;
		if (!soundHasPlayedIdCaches.containsKey(sound)) {
			array = new Array<Long>();
			soundHasPlayedIdCaches.put(sound, array);
		} else {
			array = soundHasPlayedIdCaches.get(sound);
		}
		if (!array.contains(id, false)) {
			array.add(id);
		}
	}

	private Sound _playOwnSound(String name) {
		return _playOwnSound(name, false);
	}

	private Sound _playOwnSound(String name, boolean isLooping) {
		Sound sound = _getLoadOwnSound(name);
		if (sound != null && isSoundOn) {
			long id = sound.play(soundVolume);
			sound.setLooping(id, isLooping);
			_addSoundPlayedIdCache(sound, id);
			lastSoundId = id;
		}
		return sound;
	}

	private void _stopAllCachedSounds() {
		for (Entry<Sound, Array<Long>> e : soundHasPlayedIdCaches.entrySet()) {
			for (long id : e.getValue()) {
				e.getKey().stop(id);
			}
		}
		soundHasPlayedIdCaches.clear();
	}

	private void _pauseAllCachedSounds() {
		for (Entry<Sound, Array<Long>> e : soundHasPlayedIdCaches.entrySet()) {
			for (long id : e.getValue()) {
				e.getKey().pause(id);
			}
		}
	}

	private void _resumeAllCachedSounds() {
		for (Entry<Sound, Array<Long>> e : soundHasPlayedIdCaches.entrySet()) {
			for (long id : e.getValue()) {
				e.getKey().resume(id);
			}
		}
	}

	private void _disposeOwnSounds() {
		for (Sound sound : ownSoundPool.values()) {
			sound.dispose();
		}
		ownSoundPool.clear();
	}

	private Music _playMusic(String musicName) {
		if (music != null) {
			if (!this.musicName.equals(musicName)) {
				music.dispose();
				music = null;
			} else {
				if (isMusicOn) {
					music.play();
					music.setVolume(musicVolume);
				}
				return music;
			}
		}
		FileHandle fh = RM.getFile(musicName);
		if (!fh.exists()) {
			GM.logger.error(TAG, "PlayMusic> music named [" + musicName + "] not found!");
			return null;
		}
		music = Gdx.audio.newMusic(RM.getFile(musicName));
		if (music == null) {
			GM.logger.error(TAG, "PlayMusic> music named [" + musicName + "] read error!");
			return null;
		}
		if (isMusicOn) {
			music.play();
			music.setVolume(musicVolume);
		}
		music.setLooping(true);
		this.musicName = musicName;
		return music;
	}

	private void _setMusicOn(boolean isOn) {
		if (isMusicOn != isOn) {
			isMusicOn = isOn;
			for (CallBackObj<Boolean> call : musicStateChangeCalls) {
				call.call(isOn);
			}

			if (music != null) {
				if (isMusicOn && !music.isPlaying()) {
					music.play();
					music.setVolume(musicVolume);
				} else if (!isMusicOn && music.isPlaying()) {
					music.pause();
				}
			}
		}
	}

	private void _setSoundOn(boolean isOn) {
		if (isSoundOn != isOn) {
			isSoundOn = isOn;
			for (CallBackObj<Boolean> call : soundStateChangeCalls) {
				call.call(isOn);
			}
			if (isOn) {
				_resumeAllCachedSounds();
			} else {
				_pauseAllCachedSounds();
			}
		}
	}

	private void _dispose() {
		if (music != null) {
			music.dispose();
			music = null;
		}
		_disposeOwnSounds();
	}

}
